#include "pix.hpp"
#include <QDebug>

Pix::Pix(MainWindow *parent)
{
    moveToThread(this);
    this->mutex = new QMutex(QMutex::NonRecursive);

    this->parent = parent;

    // Connect Signals to MainWindow
    connect(parent, SIGNAL(reset()), this, SLOT(reset()));
    connect(parent, SIGNAL(parseLstFile(QString)), this, SLOT(readCodeFile(QString)));
    connect(parent, SIGNAL(stop()), this, SLOT(onStop()));
    connect(parent, SIGNAL(start()), this, SLOT(onStart()));
    connect(parent, SIGNAL(trace()), this, SLOT(onTrace()));

    this->start(QThread::LowestPriority);
}

Pix::~Pix()
{
    if (this->isRunning())
	this->onStop();

    delete this->decoder;
    delete this->programManagement;
    delete this->dataMemoryOrganisation;
    delete this->stackManagement;
    delete this->watchdog;
    delete this->cpu;
    delete this->hardwareInterface;
    delete this->mutex;
    delete this->timer;
}


void Pix::initialize()
{
    QMutexLocker locker(this->mutex);

    disconnect(this->timer, SIGNAL(timeout()), this, SLOT(initialize()));

    this->hardwareInterface = new HardwareInterface(this);
    this->dataMemoryOrganisation = new DataMemoryOrganisation(this);
    this->hardwareInterface->setDataMemoryOrganisation(this->dataMemoryOrganisation);
    this->decoder = new Decoder(this);
    this->programManagement = new ProgramManagement(this);
    this->stackManagement = new StackManagement(this);
    this->watchdog = new Watchdog(this->dataMemoryOrganisation, this);
    this->cpu = new CPU(this->dataMemoryOrganisation, this->stackManagement, this->watchdog, this);
    this->programCounter = 0;
    this->dataMemoryOrganisation->getRegister(2)->setValue(0);
    this->dataMemoryOrganisation->getRegister(0xA)->setValue(0);
    this->waitInterval = 100;
    this->fastMode = false;
    this->interrupt = false;

    // Connects
    // DataMemoryOrganisation
    connect(this->parent, SIGNAL(requestChangeBank(bool)), this->dataMemoryOrganisation, SLOT(onRequestSwitchBank(bool)));
    connect(this->dataMemoryOrganisation, SIGNAL(registerChanged(Register*)), this, SLOT(onRegisterChanged(Register*)));
    connect(this->dataMemoryOrganisation, SIGNAL(switchBankSignal(bool)), this, SIGNAL(switchBankSignal(bool)));
    connect(this->dataMemoryOrganisation, SIGNAL(interrupt()), this, SLOT(onInterrupt()));
    connect(this, SIGNAL(requestChangeRegister(int,int)), this->dataMemoryOrganisation, SLOT(onRequestChangeRegister(int,int)));

    // Decoder
    connect(this->decoder, SIGNAL(customHighlightingRule(QString,int)), this, SIGNAL(customHighlightingRule(QString,int)));
    connect(this->decoder, SIGNAL(customTooltip(QString,QString)), this, SIGNAL(customTooltip(QString,QString)));
    connect(this->decoder, SIGNAL(variableFound(QString,QString)), this, SIGNAL(variableFound(QString,QString)));

    // ProgramManagement
    connect(this, SIGNAL(requestBreakpoint(int)), this->programManagement, SLOT(onRequestBreakpoint(int)));
    connect(this->programManagement, SIGNAL(breakPointToggled(int,bool,QString,QString)), this, SIGNAL(breakPointToggled(int,bool,QString,QString)));

    // StackManagement
    connect(this->stackManagement, SIGNAL(popStack()), this, SIGNAL(popStack()));
    connect(this->stackManagement, SIGNAL(pushStack(int,int)), this, SIGNAL(pushStack(int,int)));
    connect(this->stackManagement, SIGNAL(stackOverflow()), this, SIGNAL(stackOverflow()));

    // HardwareInterface
    connect(this->hardwareInterface, SIGNAL(dataRead(QString)), this, SIGNAL(dataRead(QString)));
    connect(this->hardwareInterface, SIGNAL(dataWritten(QString)), this, SIGNAL(dataWritten(QString)));
    connect(this->hardwareInterface, SIGNAL(error(QString,QString)), this, SIGNAL(hardwareInterfaceError(QString,QString)));
    connect(this->hardwareInterface, SIGNAL(stateChanged(bool)), this, SIGNAL(hardwareInterfaceStateChanged(bool)));
    connect(this->hardwareInterface, SIGNAL(mclrReceived()), this, SLOT(onHardwareMCLR()));

    // CPU
    connect(this->cpu, SIGNAL(interrupt()), this, SLOT(onInterrupt()));
}

void Pix::readCodeFile(QString filepath)
{
    QMutexLocker locker(this->mutex);

    //Open file and decode the file line by line
    QFile file(filepath);
    if (file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
	while (!file.atEnd()) {
	    Command temp = decoder->decodeLstString(file.readLine());
	    programManagement->addCommand(temp);
	}
	file.close();
    }

    emit codeFileParsed();
}

ProgramManagement *Pix::getProgramManagement()
{
    if (currentThread() != this)
    {
	ProgramManagement *prog = new ProgramManagement(*this->programManagement);
	prog->moveToThread(currentThread());
	return prog;
    }

    return programManagement;
}

DataMemoryOrganisation *Pix::getDataMemoryOrganisation()
{
    if (currentThread() != this)
    {
	DataMemoryOrganisation *dmo = new DataMemoryOrganisation(*this->dataMemoryOrganisation);
	dmo->moveToThread(currentThread());
	return dmo;
    }

    return dataMemoryOrganisation;
}

Decoder *Pix::getDecoder() const
{
    if (currentThread() != this)
    {
	Decoder *dec = new Decoder(*this->decoder);
	dec->moveToThread(currentThread());
	return dec;
    }

    return decoder;
}

StackManagement *Pix::getStackManagement() const
{
    if (currentThread() != this)
    {
	StackManagement *sm = new StackManagement(*this->stackManagement);
	sm->moveToThread(currentThread());
	return sm;
    }

    return stackManagement;
}

Watchdog *Pix::getWatchdog() const
{
    if (currentThread() != this)
    {
	Watchdog *watch = new Watchdog(*this->watchdog);
	watch->moveToThread(currentThread());
	return watch;
    }

    return watchdog;
}

CPU *Pix::getCPU() const
{
    if (currentThread() != this)
    {
	CPU *cpu = new CPU(*this->cpu);
	cpu->moveToThread(currentThread());
	return cpu;
    }

    return cpu;
}

void Pix::setSimulationMode(simulationModeEnum simulationMode)
{
    QMutexLocker locker(this->mutex);
    this->simulationMode = simulationMode;
}


void Pix::reset()
{
    QMutexLocker locker(this->mutex);

    if (currentThread() != this)
    {
	locker.unlock();
	QMetaObject::invokeMethod(this, "reset", Qt::DirectConnection);
    } else {
	//Reset Pix
	this->decoder->reset();
	this->programManagement->reset();
	this->stackManagement->reset();
	this->watchdog->clearWatchdogTimer();
	this->dataMemoryOrganisation->reset();
	this->simulationMode = Pix::STOP;
	this->programCounter = 0;
	this->dataMemoryOrganisation->getRegister(2)->setValue(0);
	this->dataMemoryOrganisation->getRegister(0xA)->setValue(0);
	//emit programCounterChanged(programCounter);
    }
}


void Pix::resetToProgramStart()
{
    QMutexLocker locker(this->mutex);

    this->stackManagement->reset();
    this->cpu->reset();
    this->dataMemoryOrganisation->reset();
    this->simulationMode = Pix::STOP;
    this->programCounter = 0;
    this->dataMemoryOrganisation->getRegister(2)->setValue(0);
    this->dataMemoryOrganisation->getRegister(0xA)->setValue(0);
    this->watchdog->clearWatchdogTimer();
    //emit programCounterChanged(programCounter);
}


void Pix::onStop()
{
    QMutexLocker locker(this->mutex);

    this->simulationMode = STOP;
    this->setTerminationEnabled(true);

    locker.unlock();
    this->resetToProgramStart();
}


void Pix::onPause()
{
    QMutexLocker locker(this->mutex);

    this->simulationMode = PAUSE;
}


void Pix::run()
{
    this->timer = new QTimer();
    connect(this->timer, SIGNAL(timeout()), this, SLOT(initialize()), Qt::DirectConnection);
    this->timer->start(0);
    exec();

    //this->deleteLater();
}


void Pix::startExecution()
{
    emit simulationStateChanged(true, this->programCounter);
    this->setTerminationEnabled(false);

    while (programManagement->getCommandMap()->contains(programCounter))
    {
	if(this->simulationMode == Pix::SLEEP)
	{
	    watchdog->incrementWatchdogTimer();
	    usleep(1);

	} else {
	    programCounter = cpu->executeCommand(&programManagement->getCommandMap()->find(programCounter).value(), programCounter);

	    if(interrupt)
	    {
		this->stackManagement->push(programCounter);
		programCounter = 4;
		dataMemoryOrganisation->getActualBank()->getRegister(0xB)->clearBit(7);
		interrupt = false;
	    }
	    emit programCounterChanged(programCounter);
	    this->dataMemoryOrganisation->getRegister(2)->setValue(programCounter);
	    this->dataMemoryOrganisation->getRegister(0xA)->setValue((programCounter >> 8) & 31);

	    // Check for Breakpoint
	    if (this->programManagement->hasBreakpointOnLine(programCounter))
		this->simulationMode = Pix::BREAK;

	    // Check if Simulation should be stopped
	    if (simulationMode == Pix::STOP)
		this->resetToProgramStart();
	    if (simulationMode != Pix::RUN)
		break;

	    // Yield Thread after each execution cycle to update GUI
	    QThread::yieldCurrentThread();
	    QApplication::processEvents();
	    // Fast Mode
	    if (!this->fastMode)
		msleep(this->waitInterval);
	    else
		usleep(0);
	}
    }

    emit programCounterChanged(programCounter);
    emit simulationStateChanged(false, this->programCounter);
}


void Pix::setFastMode(bool state)
{
    QMutexLocker locker(this->mutex);

    if (state)
    {
	this->waitInterval = 1;
	this->fastMode = true;
    } else {
	this->waitInterval = 25;
	this->fastMode = false;
    }
}


int Pix::getProgramCounter() const
{
    return this->programCounter;
}

void Pix::onInterrupt()
{
    this->interrupt = true;
}


void Pix::onRequestChangeRegister(int address, int value)
{
    Register *reg = this->dataMemoryOrganisation->getRegister(address);

    reg->setValue(value, true);
}


void Pix::onChangeHardwareInterfaceState(QString name, bool state)
{
    if (state)
	this->hardwareInterface->setSerialPort(name);
    else
	this->hardwareInterface->stop();
}


void Pix::hwInterfaceManualSend()
{
    this->hardwareInterface->processSerialPortQuery();
}


void Pix::hwInterfaceManualReceive(QString data)
{
    this->hardwareInterface->onManualReceive(data);
}


QString Pix::getRuntimeString() const
{
    return this->cpu->getRuntimeAsString();
}


QString Pix::getWatchdogString() const
{
    return this->watchdog->toString();
}


Pix::simulationModeEnum Pix::getSimulationMode() const
{
    return this->simulationMode;
}


HardwareInterface *Pix::getHardwareInterface()
{
    return this->hardwareInterface;
}


void Pix::onStart()
{
    QMutexLocker locker(this->mutex);

    if (currentThread() != this)
    {
	locker.unlock();
	QMetaObject::invokeMethod(this, "onStart", Qt::DirectConnection);
    } else {
	this->simulationMode = Pix::RUN;

	locker.unlock();
	this->startExecution();
    }
}


void Pix::onTrace()
{
    QMutexLocker locker(this->mutex);

    if (currentThread() != this)
    {
	locker.unlock();
	QMetaObject::invokeMethod(this, "onTrace", Qt::DirectConnection);
    } else {
	this->simulationMode = Pix::TRACE;

	locker.unlock();
	this->startExecution();
    }
}


void Pix::onRegisterChanged(Register *reg)
{
    emit registerChanged(reg);
    //QMetaObject::invokeMethod(this->parent, "onRegisterChanged", Qt::DirectConnection, Q_ARG(Register*, reg));
}


void Pix::onHardwareMCLR()
{
    QMutexLocker locker(this->mutex);

    this->simulationMode = Pix::STOP;
    msleep(5);
    this->stackManagement->reset();
    this->cpu->reset();
    this->dataMemoryOrganisation->resetNormalOperation();
    this->programCounter = 0;
    this->watchdog->clearWatchdogTimer();
    emit programCounterChanged(programCounter);
}
